import SEO from "../components/SEO";
import { TOC, TOCList, TOCLink } from "../components/TOC";
import { AsPropWarning } from "../components/AsPropWarning";
import { Pipe } from "../components/Pipe";

<SEO
	title="Combobox"
	description="Accessible combobox (autocomplete or autosuggest) component for React"
/>

# Combobox

<TOC>
	<TOCList>
		<TOCLink href="#combobox-1">Combobox</TOCLink>
		<TOCLink href="#comboboxinput">ComboboxInput</TOCLink>
		<TOCLink href="#comboboxpopover">ComboboxPopover</TOCLink>
		<TOCLink href="#comboboxlist">ComboboxList</TOCLink>
		<TOCLink href="#comboboxoption">ComboboxOption</TOCLink>
		<TOCLink href="#comboboxoptiontext">ComboboxOptionText</TOCLink>
		<TOCLink href="#usecomboboxcontext">useComboboxContext</TOCLink>
		<TOCLink href="#usecomboboxoptioncontext">useComboboxOptionContext</TOCLink>
	</TOCList>
</TOC>

- Source: https://github.com/reach/reach-ui/tree/main/packages/combobox
- WAI-ARIA: https://www.w3.org/TR/wai-aria-practices-1.2/#combobox

Accessible combobox (autocomplete or autosuggest) component for React.

A combobox is the combination of an `<input type="text"/>` and a list. The list is designed to help the user arrive at a value, but the value does not necessarily have to come from that list. Don't think of it like a `<select/>`, but more of an `<input type="text"/>` with some suggestions. You can, however, validate that the value comes from the list, that's up to your app.

## Installation

From the command line in your project directory, run `npm install @reach/combobox` or `yarn add @reach/combobox`. Then import the components and styles that you need:

```bash
npm install @reach/combobox
# or
yarn add @reach/combobox
```

```js
import {
	Combobox,
	ComboboxInput,
	ComboboxPopover,
	ComboboxList,
	ComboboxOption,
	ComboboxOptionText,
} from "@reach/combobox";
import "@reach/combobox/styles.css";
```

## Accessibility

Reach UI aims to handle most ARIA and accessibility concerns so that developers don't have to worry about it. Labeling is often the one thing Reach can't do for you by default since there are many ways to accomplish it, and some of those methods require app-level context.

However, we still aim to make accessibility as easy as possible. Labels for the compound `Combobox` component can go on the parent and we will forward the label to the correct nested component where it belongs.

For instance, instead of adding `aria-label` to `<ComboboxInput>`, we can add it to `<Combobox>`. The same goes for `aria-labelledby`.

```jsx
<Combobox aria-label="choose a fruit">
	<ComboboxInput />
	<ComboboxPopover>
		<ComboboxList>
			<ComboboxOption value="Apple" />
			<ComboboxOption value="Banana" />
		</ComboboxList>
	</ComboboxPopover>
</Combobox>
```

One benefit reaped from this pattern is that it alleviates the need for developers to think about where the `aria-label` or `aria-labelledby` attributes belong in the component tree. Another benefit is that if the ARIA spec changes in the future (as it did from 1.1 to 1.2 for `Combobox`), Reach doesn't introduce a breaking API change to make accessibility improvements.

> **NOTE:** You can still pass either `aria-label` or `aria-labelledby` directly to `ComboboxInput` if you'd prefer and those values will override either respective prop passed into `Combobox`, though we discourage this. It's helpful for the component's context to keep a reference to its label. We may remove this option in a future release.

## Examples

To get you started, let's take a look at a few examples that grow from simple to complex, after the examples you can see the API for each component.

### Basic, Fixed List Combobox

Like a `<table><tr><td/></tr></table>`, a full combobox is made up of multiple components. This example demonstrates all of the pieces you need in the simplest form possible.

```jsx
// jsx-demo
function Example() {
	return (
		<div>
			<h4 id="demo">Basic, Fixed List Combobox</h4>
			<Combobox aria-labelledby="demo">
				<ComboboxInput />
				<ComboboxPopover>
					<ComboboxList>
						<ComboboxOption value="Apple" />
						<ComboboxOption value="Banana" />
						<ComboboxOption value="Orange" />
						<ComboboxOption value="Pineapple" />
						<ComboboxOption value="Kiwi" />
					</ComboboxList>
				</ComboboxPopover>
			</Combobox>
		</div>
	);
}
```

### Custom Rendering in ComboboxOption

Sometimes your items need to be more than just text, in these cases you can pass children to `ComboboxOption`, and then render a `<ComboboxOptionText/>` to keep the built-in text highlighting. Only the `value` is used to match, not the children.

```jsx
// jsx-demo
function Example() {
	return (
		<Combobox aria-label="custom option demo">
			<ComboboxInput
				placeholder="Custom Option Rendering"
				style={{ width: 300 }}
			/>
			<ComboboxPopover>
				<ComboboxList>
					<ComboboxOption value="Apple">
						🍎 <ComboboxOptionText />
					</ComboboxOption>
					<ComboboxOption value="Banana">
						🍌 <ComboboxOptionText />
					</ComboboxOption>
					<ComboboxOption value="Orange">
						🍊 <ComboboxOptionText />
					</ComboboxOption>
					<ComboboxOption value="Pineapple">
						🍍 <ComboboxOptionText />
					</ComboboxOption>
					<ComboboxOption value="Kiwi">
						🥝 <ComboboxOptionText />
					</ComboboxOption>
				</ComboboxList>
			</ComboboxPopover>
		</Combobox>
	);
}
```

### Clientside Search

This demo searches a client-side list of all US Cities. Combobox does not implement any matching on your list (aside from highlighting the matched phrases in an option). Instead, you render an Option for each result you want in the list. So your job is to:

- Establish the search term state
- Match the search to your list
- Render a `ComboboxOption` for each match

There is nothing special about managing state for a combobox, it's like managing state for any other list in your app. As the input changes, you figure out what state you need, then render as many ComboboxOption elements as you want.

```jsx
// jsx-demo
(() => {
	function Example() {
		const [term, setTerm] = React.useState("");
		const results = useCityMatch(term);
		const handleChange = (event) => setTerm(event.target.value);

		return (
			<div>
				<h4>Clientside Search</h4>
				<Combobox aria-label="Cities">
					<ComboboxInput
						className="city-search-input"
						onChange={handleChange}
					/>
					{results && (
						<ComboboxPopover className="shadow-popup">
							{results.length > 0 ? (
								<ComboboxList>
									{results.slice(0, 10).map((result, index) => (
										<ComboboxOption
											key={index}
											value={`${result.city}, ${result.state}`}
										/>
									))}
								</ComboboxList>
							) : (
								<span style={{ display: "block", margin: 8 }}>
									No results found
								</span>
							)}
						</ComboboxPopover>
					)}
				</Combobox>
			</div>
		);
	}

	function useCityMatch(term) {
		const throttledTerm = useThrottle(term, 100);
		return React.useMemo(
			() =>
				term.trim() === ""
					? null
					: matchSorter(cities, term, {
							keys: [(item) => `${item.city}, ${item.state}`],
					  }),
			[throttledTerm]
		);
	}

	return <Example />;
})();
```

### Server Side Search

This is the same demo as above, except this time we're going to a server to get the match. This is recommended as the previous example had to download 350kb of city text! Again, there is nothing special about a ComboboxList as any other list in React. As the input changes, fetch data, set state, render options.

```jsx
// jsx-demo
(() => {
	function Example() {
		const [searchTerm, setSearchTerm] = React.useState("");
		const cities = useCitySearch(searchTerm);
		const handleSearchTermChange = (event) => {
			setSearchTerm(event.target.value);
		};

		return (
			<Combobox aria-label="Cities">
				<ComboboxInput
					className="city-search-input"
					onChange={handleSearchTermChange}
				/>
				{cities && (
					<ComboboxPopover className="shadow-popup">
						{cities.length > 0 ? (
							<ComboboxList>
								{cities.map((city) => {
									const str = `${city.city}, ${city.state}`;
									return <ComboboxOption key={str} value={str} />;
								})}
							</ComboboxList>
						) : (
							<span style={{ display: "block", margin: 8 }}>
								No results found
							</span>
						)}
					</ComboboxPopover>
				)}
			</Combobox>
		);
	}

	function useCitySearch(searchTerm) {
		const [cities, setCities] = React.useState([]);

		React.useEffect(() => {
			if (searchTerm.trim() !== "") {
				let isFresh = true;
				fetchCities(searchTerm).then((cities) => {
					if (isFresh) setCities(cities);
				});
				return () => (isFresh = false);
			}
		}, [searchTerm]);

		return cities;
	}

	const cache = {};
	function fetchCities(value) {
		if (cache[value]) {
			return Promise.resolve(cache[value]);
		}
		return fetch("https://city-search.chaance.vercel.app/api?" + value)
			.then((res) => res.json())
			.then((result) => {
				cache[value] = result;
				return result;
			});
	}

	return <Example />;
})();
```

### Lots of arbitrary elements

Sometimes your list is a bit more complicated, like categories of results, and lots of elements besides options inside the popover.

You can even have other interactive elements inside the popover, it won't close when the user interacts with them.

```jsx
// jsx-demo
(() => {
	function Example() {
		const [term, setTerm] = React.useState("");
		const results = useCityMatch(term);
		const handleChange = (event) => setTerm(event.target.value);

		return (
			<div>
				<h4>Lots of stuff going on</h4>
				<Combobox>
					<ComboboxInput
						onChange={handleChange}
						style={{ width: 300, margin: 0 }}
					/>
					{results && (
						<ComboboxPopover style={{ width: 300 }}>
							{results.length > 0 ? (
								<ComboboxList>
									<h5 style={heading}>Top 3 results!</h5>
									{results.slice(0, 3).map((result, index) => (
										<ComboboxOption
											key={index}
											value={`${result.city}, ${result.state}`}
										/>
									))}
									{results.length > 3 && (
										<React.Fragment>
											<h5 style={heading}>The Rest</h5>
											{results.slice(3, 10).map((result, index) => (
												<ComboboxOption
													key={index}
													value={`${result.city}, ${result.state}`}
												/>
											))}
										</React.Fragment>
									)}
								</ComboboxList>
							) : (
								<div>
									<p style={{ padding: 10, textAlign: "center" }}>
										No results 😞
									</p>
								</div>
							)}
							<p style={{ textAlign: "center", padding: 10 }}>
								<button>Create a new record</button>
							</p>
						</ComboboxPopover>
					)}
				</Combobox>
			</div>
		);
	}

	function useCityMatch(term) {
		const throttledTerm = useThrottle(term, 100);
		return React.useMemo(
			() =>
				term.trim() === ""
					? null
					: matchSorter(cities, term, {
							keys: [(item) => `${item.city}, ${item.state}`],
					  }),
			[throttledTerm]
		);
	}

	const heading = {
		fontSize: "100%",
		color: "red",
		fontWeight: "bold",
		textTransform: "uppercase",
		margin: 0,
		padding: 5,
	};

	return <Example />;
})();
```

### Custom styling

This demo shows how you can control a lot about the styling. It uses `portal={false}` on the ComboboxPopover which allows us to create a continuous outline around the entire thing.

```jsx
// jsx-demo
(() => {
	function Example() {
		let [term, setTerm] = React.useState("");
		let results = useCityMatch(term);
		const handleChange = (event) => setTerm(event.target.value);

		return (
			<Combobox className="pink">
				<ComboboxInput onChange={handleChange} />
				{results && (
					<ComboboxPopover portal={false}>
						<hr />
						{results.length > 0 ? (
							<ComboboxList>
								{results.slice(0, 10).map((result, index) => (
									<ComboboxOption
										key={index}
										value={`${result.city}, ${result.state}`}
									/>
								))}
							</ComboboxList>
						) : (
							<p
								style={{
									margin: 0,
									color: "#454545",
									padding: "0.25rem 1rem 0.75rem 1rem",
									fontStyle: "italic",
								}}
							>
								No results :(
							</p>
						)}
					</ComboboxPopover>
				)}
			</Combobox>
		);
	}

	function useCityMatch(term) {
		let throttledTerm = useThrottle(term, 100);
		return React.useMemo(
			() =>
				term.trim() === ""
					? null
					: matchSorter(cities, term, {
							keys: [(item) => `${item.city}, ${item.state}`],
					  }),
			[throttledTerm]
		);
	}

	return <Example />;
})();
```

## Component API

### Combobox

Parent component that sets up the proper ARIA roles and context for the rest of the components.

#### Combobox CSS Selectors

Please see the [styling guide](/styling).

```css
[data-reach-combobox] {
}

/* root element in a specific state  */
/* possible states: "idle" | "suggesting" | "navigating" | "interacting"  */
[data-reach-combobox][data-state="STATE_REF"] {
}
```

#### Combobox Props

| Prop                                   | Type                          | Required |
| -------------------------------------- | ----------------------------- | -------- |
| [`as`](#combobox-as)                   | `string` <Pipe /> `Component` | false    |
| [`children`](#combobox-children)       | `node` <Pipe /> `func`        | false    |
| [`openOnFocus`](#combobox-openonfocus) | `boolean`                     | false    |
| [`onSelect`](#combobox-onselect)       | `func`                        | false    |

##### Combobox `as`

`as?: keyof JSX.IntrinsicElements | React.ComponentType`

A string representing an HTML element or a React component that will tell the `ComboboxOption` what element to render. Defaults to `div`.

<AsPropWarning />

##### Combobox `children`

`children: React.ReactNode | ((props: { id: string | undefined; isExpanded: boolean; navigationValue: string | null; state: string }) => React.ReactNode)`

Combobox expects to receive `ComboboxInput` and `ComboboxPopover` as children. You can also pass a render function to expose data for `Combobox` to its descendants.

##### Combobox `openOnFocus`

`openOnFocus?: boolean`

```jsx
<Combobox openOnFocus />
```

Defaults to `false`.

If true, the popover opens when focus is on the text box.

##### Combobox `onSelect`

`onSelect?(value: string): void`

Called with the selection value when the user makes a selection from the list.

```jsx
<Combobox onSelect={(item) => {}} />
```

### ComboboxInput

Wraps an `<input/>` with a couple extra props that work with the combobox.

#### ComboboxInput CSS Selectors

Please see the [styling guide](/styling).

```css
[data-reach-combobox-input] {
}

/* input element in a specific state */
/* possible states: "idle" | "suggesting" | "navigating" | "interacting"  */
[data-reach-combobox-input][data-state="STATE_REF"] {
}
```

#### ComboboxInput Props

| Prop                                          | Type                          | Required |
| --------------------------------------------- | ----------------------------- | -------- |
| [`as`](#comboboxinput-as)                     | `string` <Pipe /> `Component` | false    |
| [selectOnClick](#comboboxinput-selectonclick) | `boolean`                     | false    |
| [autocomplete](#comboboxinput-autocomplete)   | `boolean`                     | false    |

##### ComboboxInput `as`

`as?: keyof JSX.IntrinsicElements | React.ComponentType`

A string representing an HTML element or a React component that will tell the `ComboboxInput` what element to render. Defaults to `input`.

> **NOTE:** Recreating native `input` behavior and all of its nuance with a non-semantic element is _extremely difficult_ and may make the component inaccessible to many users. We do not recommend doing this.

##### ComboboxInput `selectOnClick`

`selectOnClick?: boolean`

```jsx
<ComboboxInput selectOnClick />
```

Defaults to `false`.

If true, when the user clicks inside the text box the current value will be selected. Use this if the user is likely to delete all the text anyway (like the URL bar in browsers).

However, if the user is likely to want to tweak the value, leave this `false`, like a google search--the user is likely wanting to edit their search, not replace it completely.

##### ComboboxInput `autocomplete`

`autocomplete?: boolean`

Defaults to `true`.

Determines if the value in the input changes or not as the user navigates with the keyboard. If `true`, the value changes, if `false` the value doesn't change.

Set this to false when you don't really need the value from the input but want to populate some other state (like the recipient selector in Gmail). But if your input is more like a normal `<input type="text"/>`, then leave the `true` default.

```jsx
<ComboboxInput autocomplete={false} />
```

### ComboboxPopover

Contains the popup that renders the list. Because some UI needs to render more than the list in the popup, you need to render one of these around the list. For example, maybe you want to render the number of results suggested.

#### ComboboxPopover CSS Selectors

Please see the [styling guide](/styling).

```css
[data-reach-combobox-popover] {
}

/* popover element in a specific state */
/* possible states: "idle" | "suggesting" | "navigating" | "interacting"  */
[data-reach-combobox-popover][data-state="STATE_REF"] {
}
```

#### ComboboxPopover Props

| Prop                                | Type      | Required |
| ----------------------------------- | --------- | -------- |
| [`portal`](#comboboxpopover-portal) | `boolean` | false    |

##### ComboboxPopover `portal`

If you pass `<ComboboxPopover portal={false} />` the popover will not render inside of a portal, but in the same order as the React tree. This is mostly useful for styling the entire component together, like the pink focus outline in the example earlier in this page.

Defaults to `true`.

### ComboboxList

Contains the `ComboboxOption` elements and sets up the proper aria attributes for the list.

#### ComboboxList CSS Selectors

Please see the [styling guide](/styling).

```css
[data-reach-combobox-list] {
}
```

#### ComboboxList Props

| Prop                                                 | Type                          | Required |
| ---------------------------------------------------- | ----------------------------- | -------- |
| [`as`](#comboboxlist-as)                             | `string` <Pipe /> `Component` | false    |
| [`persistSelection`](#comboboxlist-persistselection) | `boolean`                     | false    |

##### ComboboxList `as`

`as?: keyof JSX.IntrinsicElements | React.ComponentType`

A string representing an HTML element or a React component that will tell the `ComboboxList` what element to render. Defaults to `ul`.

<AsPropWarning />

##### ComboboxList `persistSelection`

`persistSelection?: boolean`

```jsx
<ComboboxList persistSelection />
```

Defaults to `false`. When `true` and the list is opened, if an option's value matches the value in the input, it will automatically be highlighted and be the starting point for any keyboard navigation of the list.

This allows you to treat a Combobox more like a `<select>` than an `<input/>`, but be mindful that the user is still able to put any arbitrary value into the input, so if the only valid values for the input are from the list, your app will need to do that validation on blur or submit of the form.

### ComboboxOption

An option that is suggested to the user as they interact with the combobox.

#### ComboboxOption CSS Selectors

Please see the [styling guide](/styling).

```css
[data-reach-combobox-option] {
}

/* option element when highlighted */
[data-reach-combobox-option][data-highlighted] {
}
```

#### ComboboxOption Props

| Prop                                   | Type                          | Required |
| -------------------------------------- | ----------------------------- | -------- |
| [`as`](#comboboxoption-as)             | `string` <Pipe /> `Component` | false    |
| [`value`](#comboboxoption-value)       | `string`                      | true     |
| [`children`](#comboboxoption-children) | `node`                        | false    |

##### ComboboxOption `as`

`as?: keyof JSX.IntrinsicElements | React.ComponentType`

A string representing an HTML element or a React component that will tell the `ComboboxOption` what element to render. Defaults to `li`.

<AsPropWarning />

##### ComboboxOption `value`

`value?: string`

The value to match against when suggesting.

```jsx
<ComboboxOption value="Salt Lake City, Utah" />
```

##### ComboboxOption `children`

`children?: React.ReactNode | ((props: { value: string; index: number }) => React.ReactNode)`

Optional. If omitted, the `value` will be used as the children like this:

```jsx
<ComboboxOption value="Seattle, Tacoma, Washington" />
```

But if you need to control a bit more, you can put whatever children you want, but make sure to render a `ComboboxOptionText` as well, so the value is still displayed with the text highlighting on the matched portions.

```jsx
<ComboboxOption value="Apple" />
🍎 <ComboboxOptionText/>
</ComboboxOption>
```

### ComboboxOptionText

Renders the value of a `ComboboxOption` as text but with spans wrapping the matching and non-matching segments of text.

So given an option like this:

```jsx
<ComboboxOption value="Seattle">
	🌧 <ComboboxOptionText />
</ComboboxOption>
```

And the user typed `Sea`, the out would be:

```jsx
<span data-user-value>Sea</span><span data-suggested-value>ttle</span>
```

#### ComboboxOptionText CSS Selectors

```css
[data-reach-combobox-option-text] {
}

/* the matching segments of text */
[data-user-value] {
}

/* the unmatching segments */
[data-suggested-value] {
}
```

### useComboboxContext

`function useComboboxContext(): { id: string | undefined; isExpanded: boolean; navigationValue: string | null; state: string }`

A hook that exposes data for a given `Combobox` component to its descendants.

### useComboboxOptionContext

`function useComboboxOptionContext(): { value: string; index: number }`

A hook that exposes data for a given `ComboboxOption` component to its descendants.
